#include "Image.h"
#include <math.h>
#include <zlib.h>


#ifndef LOADER_MACROS
#define LOADER_MACROS

#define ENDIAN_SWAP(i) i=((i<<24)|(((i<<16)>>24)<<16)|(((i<<8)>>24)<<8)|(i>>24))


#define IPNG (int)2303741511
#define IHDR (int)1229472850
#define IEND (int)1229278788
#define IDAT (int)1229209940
#define PLTE (int)1347179589

#endif
void Image::Load(string Filename){
	int BytesPerPixel; //will be 4 by the end of this...
	unsigned int Sig=0;
	bool Done=false;
	bool Paletted=false;
	bool Adam7Interlace=false;
	unsigned int Length=0;
	unsigned int Type=0;
    BYTE Filtertype = NULL; //harmless
	BYTE *Data= NULL;
	BYTE *DecompressedData= NULL;
	BYTE *DefilteredData= NULL;
	BYTE *Palette= NULL;
	//Unzipper unz = Unzipper(); This works on small datastreams... but Id rather not risk it.
	z_stream z;
			z.zalloc = Z_NULL;
			z.zfree = Z_NULL;
			z.opaque = Z_NULL;
			z.avail_in = 0;
			z.next_in = Z_NULL;
	int ret;
	int CRC=0;
	int Dataplace=0;

	
	//Load File + Check Sig
	ifstream File(Filename.c_str(),ifstream::binary);
	if(!File.good()){MessageBoxA ( NULL,"Error Opening PNG", "Error Reading Image...", MB_OK );}
	Sig = File.peek();
	File.read((char*)&Sig,sizeof(int));
	ENDIAN_SWAP(Sig);
	if(Sig != IPNG ){MessageBoxA ( NULL,"Invalid PNG Identifyer.", "Image Error", MB_OK );
		File.close();File.open("Data/Image/Misc/Error.png",ifstream::binary);File.read((char*)&Sig,sizeof(int));} //load Error if wrong filetype
	File.read((char*)&Sig,sizeof(int));//Thar be 4 more bytes that vary...

	
    // Read Chunk, its always (LENGTH,TYPE,DATA,CRC)
	while(!Done&&File.good()){
	File.read((char*)&Length,sizeof(int));
	ENDIAN_SWAP(Length);
	File.read((char*)&Type,sizeof(int));
	ENDIAN_SWAP(Type);
	//ENDIAN_SWAP(Type); already done

		switch(Type){
		case IHDR:{
			File.read((char*)&Width,sizeof(int));
			ENDIAN_SWAP(Width);
			File.read((char*)&Height,sizeof(int));
			ENDIAN_SWAP(Height);
			File.get(); //Bit Depth
					switch(File.get()){ //color type
					case 0:BytesPerPixel=1;break;//greyscale
					case 2:BytesPerPixel=3;break;//RBG
					case 3:BytesPerPixel=1;// INDEX INTO PALLATE
							Paletted = true;break;
					case 4:BytesPerPixel=2;break;// GRAY,A
					case 6:BytesPerPixel=4;break; //RGBA
					default: break;}
			File.get();//Compression Method. Is DEFLATE.
			File.get(); //Filter Method. If this not be 0, we got problems
			Adam7Interlace = (File.get())? true:false; //pixel interlace method, 0 is none, 1 is ME7
			DecompressedData = new BYTE[(Height+1)*Width*BytesPerPixel];
			z.avail_out = ((Height+1)*Width*BytesPerPixel);
			z.next_out  = &DecompressedData[0];
			ret = inflateInit(&z);
			break;}
		case IEND:{Done = true;break;}
		case PLTE:{
			if((Length%3 !=0) |(Paletted== false)){MessageBoxA ( NULL,"Invalid Palette?", "Image Error", MB_OK );}
			Palette = new BYTE[Length];
			File.read((char*)Palette,Length);break;}
		case IDAT:{ //Compressed, Possibly Filtered and/or Palleted Data
			Data = new BYTE[Length];
			File.read((char*)Data,Length);
			z.avail_in = Length;
			z.next_in = &Data[0]; //2 bytes uneccesary dat
  			    ret = inflate(&z,Z_NO_FLUSH);
				if(ret == Z_STREAM_END)inflateEnd(&z);
			delete Data;
			break;}

		default:for(int i=0; i<Length;i++){File.get();}break;}
		File.read((char*)&CRC,sizeof(int)); //unused. Dont hex edit your pics kids.
	}

	

	DefilteredData = new BYTE[Width*Height*BytesPerPixel]; //get rid or random byte in front
	Defilter(DecompressedData,DefilteredData,BytesPerPixel);	
	delete DecompressedData;
	ImageData = new BYTE[Width*Height*4];//Always convert to RGBA
	if(BytesPerPixel != 4){
		if(Paletted){Depalette(DefilteredData,ImageData,Palette);delete Palette;}
		else{ConvertToRGBA(BytesPerPixel,DefilteredData,ImageData);}
	}
	else{memcpy(ImageData,DefilteredData,Width*Height*4);}
	delete DefilteredData;
	if(Adam7Interlace ==true){/*De_lace(ImageData);*/} //wont even bother photoshop can delace any imported things

//OGL texstuff

	glEnable( GL_TEXTURE_2D ); 
	glActiveTexture(GL_TEXTURE0);
	glGenTextures(1,&TexID); //create TexBuffer
	glBindTexture(GL_TEXTURE_2D,TexID); //Finalize and make it current
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
	glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA8,Width,Height,0,GL_RGBA,GL_UNSIGNED_BYTE,getImageData());

	glDisable( GL_TEXTURE_2D ); 
//EL FIN
}

void Image::Depalette(BYTE *in, BYTE *out,BYTE *Palette){
int Write =0;
		for(int i=0; Write< (Width*Height*4);i++){
	    out[Write]  =Palette[(in[i]*3)];
		out[Write+1]=Palette[(in[i]*3)+1];
		out[Write+2]=Palette[(in[i]*3)+2];
		out[Write+3]=255;
		Write+=4;}
}

BYTE PAETH(BYTE a, BYTE b, BYTE c){
	int p = a + b - c;
	if ((abs(p - a) <= abs(p - b))&&(abs(p - a) <=  abs(p - c))){return a;}
    else if (abs(p - b) <=  abs(p - c))return b;
    else return c;}

void Image::Defilter(BYTE *in,BYTE *out,int BPP){
	BYTE fil= in[0]; 
	unsigned int line = Width*BPP;

	for(int j=0; j<line; j++){out[j] =in[j+1];}

	if(fil==1){for(int j=0; j<line; j++){out[j] += (j-BPP >= 0)?out[j-BPP]:0;} }//Specialty thing

	for(int i=1; i<Height;i++){
		fil = in[i*(line + 1)];
		for(int j=0; j<line; j++){out[i*line + j] =in[i*(line + 1)+j+1];}
		switch(fil){
		case 0: break;
		//DUMB MISTAKE: when using the pixel to the left because it is treated as 0
		case 1:{//out[i*line] +=0;
			for(int j=BPP; j<line; j++){out[i*line+j] +=out[i*line+j-BPP];}break;} //Last

		case 2:{for(int j=0; j<line; j++){out[i*line+j] += out[(i-1)*line + j];}break;} //Up

		case 3:{for(Byte f=0; f<BPP;f++){out[i*line+f] +=out[(i-1)*line+f]/2;}
			for(int j=BPP; j<line; j++){out[i*line+j] +=((out[i*line+j-BPP]+out[(i-1)*line + j])/2);}break;} //AVG(Up,Last)

		case 4:{for(Byte f=0; f<BPP;f++){out[i*line+f] +=PAETH(0,out[(i-1)*line + f],0);}
			for(int j=BPP; j<line; j++){out[i*line+j] += PAETH(out[i*line+j-BPP],out[(i-1)*line + j],out[(i-1)*line + j-BPP]);}break;} //PAETH(Last,Up,Up+Last)
		
		default:MessageBoxA ( NULL,"Invalid filter" , "Error Reading Image...", MB_OK );break;
		}
	}
}

void Image::ConvertToRGBA(int pix,BYTE *in, BYTE *out){
unsigned int Write =0;
switch(pix){
case 1:
		for(int i=0; Write< (Width*Height*4);i++){
	    out[Write]  =in[i];
		out[Write+1]=in[i];
		out[Write+2]=in[i];
		out[Write+3]=255;
		Write+=4;}break;
case 2:
		for(int i=0; Write< (Width*Height*4);i+=2){
	    out[Write]  =in[i];
		out[Write+1]=in[i];
		out[Write+2]=in[i];
		out[Write+3]=in[i+1];
		Write+=4;}break;
case 3:
		for(unsigned int i=0; Write<(Width*Height*4);i+=3){
		out[Write]=  in[i];
		out[Write+1]=in[i+1];
		out[Write+2]=in[i+2];
		out[Write+3]=255;
		Write+=4;}break;
default:break;
 }
}

const unsigned char* Image::getImageData() const
{
    return &ImageData[0];
} // thank you opengl book!

